
#pragma once

#include <atltheme.h>

class CTitleTip : public CWindowImpl< CTitleTip >
{
public:
    CTitleTip()
    {
        m_hWndParent = NULL;
        m_bShowThemed = TRUE;
    }

    ~CTitleTip()
    {
    }

    DECLARE_WND_CLASS_EX(_T("TitleTip"), CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS | CS_SAVEBITS, COLOR_WINDOW)

protected:
    HWND m_hWndParent;
    BOOL m_bShowThemed;
    CString m_strToolTip;

    COLORREF m_rgbBackground;
    COLORREF m_rgbTextColour;
    COLORREF m_rgbBorderOuter;
    COLORREF m_rgbBorderInner;
    COLORREF m_rgbBackgroundTop;
    COLORREF m_rgbBackgroundBottom;

    CFont m_fntTitleFont;
    CToolTipCtrl m_ttToolTip;

public:
    BOOL Create(HWND hWndParent, BOOL bShowThemed)
    {
        m_hWndParent = hWndParent;
        m_bShowThemed = bShowThemed;
        m_rgbBackground = GetSysColor(COLOR_INFOBK);
        m_rgbTextColour = (m_bShowThemed && CTheme::IsThemingSupported()) ? GetSysColor(COLOR_WINDOWTEXT) : GetSysColor(COLOR_INFOTEXT);
        m_rgbBorderOuter = RGB(220, 220, 220);
        m_rgbBorderInner = RGB(245, 245, 245);
        m_rgbBackgroundTop = RGB(250, 250, 250);
        m_rgbBackgroundBottom = RGB(235, 235, 235);

        if (CWindowImpl< CTitleTip >::Create(hWndParent, NULL, NULL, WS_POPUP, WS_EX_TOOLWINDOW | WS_EX_TOPMOST) == NULL)
            return FALSE;

        // create the tooltip
        if (!m_ttToolTip.Create(m_hWnd))
            return FALSE;

        m_ttToolTip.SetMaxTipWidth(SHRT_MAX);
        // get system message font
        CLogFont logFont;
        logFont.SetMessageBoxFont();

        if (!m_fntTitleFont.IsNull())
            m_fntTitleFont.DeleteObject();

        return (m_fntTitleFont.CreateFontIndirect(&logFont) != NULL);
    }

    BOOL Show(CRect& rcRect, LPCTSTR lpszItemText, LPCTSTR lpszToolTip)
    {
        CString strItemText = lpszItemText;

        if (!IsWindow() || strItemText.IsEmpty())
            return FALSE;

        m_strToolTip = lpszToolTip;
        SetWindowText(strItemText);
        CClientDC dcClient(m_hWnd);
        HFONT hOldFont = dcClient.SelectFont(m_fntTitleFont);
        CRect rcTextExtent(rcRect);
        // calculate item text extent...
        dcClient.DrawText(strItemText, strItemText.GetLength(), rcTextExtent, DT_LEFT | DT_SINGLELINE | DT_NOPREFIX | DT_VCENTER | DT_CALCRECT);
        dcClient.SelectFont(hOldFont);

        // do not show titletip if entire text is visible
        if (rcTextExtent.Width() <= rcRect.Width() - 1)
            return FALSE;

        if (m_strToolTip.IsEmpty())
            m_ttToolTip.Activate(FALSE);
        else {
            m_ttToolTip.Activate(TRUE);
            m_ttToolTip.AddTool(m_hWnd, (LPCTSTR)m_strToolTip.Left(SHRT_MAX));
        }

        // show titletip at new location
        if (!SetWindowPos(NULL, rcRect.left - 4, rcRect.top, rcTextExtent.Width() + 11, rcRect.Height(), SWP_NOZORDER | SWP_SHOWWINDOW | SWP_NOACTIVATE | SWP_NOCOPYBITS))
            return FALSE;

        SetCapture();
        return TRUE;
    }

    BOOL Hide()
    {
        if (GetCapture() == m_hWnd)
            ReleaseCapture();

        return IsWindow() ? ShowWindow(SW_HIDE) : FALSE;
    }

    BEGIN_MSG_MAP_EX(CTitleTip)
    MSG_WM_DESTROY(OnDestroy)
    MESSAGE_RANGE_HANDLER_EX(WM_MOUSEFIRST, WM_MOUSELAST, OnMouseRange)
    MSG_WM_ERASEBKGND(OnEraseBkgnd)
    MSG_WM_PAINT(OnPaint)
    END_MSG_MAP_EX()

    void OnDestroy()
    {
        if (m_ttToolTip.IsWindow())
            m_ttToolTip.DestroyWindow();

        m_ttToolTip.m_hWnd = NULL;
    }

    LRESULT OnMouseRange(UINT nMessage, WPARAM wParam, LPARAM lParam)
    {
        SetMsgHandled(FALSE);

        if (m_ttToolTip.IsWindow()) {
            MSG msgRelay = { m_hWnd, nMessage, wParam, lParam };
            m_ttToolTip.RelayEvent(&msgRelay);
        }

        CPoint ptMouse(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
        ClientToScreen(&ptMouse);

        if (nMessage == WM_MOUSEMOVE) {
            CRect rcWindow;
            GetWindowRect(rcWindow);

            if (!rcWindow.PtInRect(ptMouse))
                Hide();

            return 0;
        }

        CWindow wndParent(m_hWndParent);
        UINT nHitTest = wndParent.SendMessage(WM_NCHITTEST, 0, MAKELPARAM(ptMouse.x, ptMouse.y));

        // forward notifcation through to parent
        if (nHitTest == HTCLIENT) {
            wndParent.ScreenToClient(&ptMouse);
            wndParent.PostMessage(nMessage, wParam, MAKELPARAM(ptMouse.x, ptMouse.y));
        } else {
            switch (nMessage) {
            case WM_LBUTTONDOWN:
                wndParent.PostMessage(WM_NCLBUTTONDOWN, nHitTest, MAKELPARAM(ptMouse.x, ptMouse.y));
                break;

            case WM_LBUTTONUP:
                wndParent.PostMessage(WM_NCLBUTTONUP, nHitTest, MAKELPARAM(ptMouse.x, ptMouse.y));
                break;

            case WM_LBUTTONDBLCLK:
                wndParent.PostMessage(WM_NCLBUTTONDBLCLK, nHitTest, MAKELPARAM(ptMouse.x, ptMouse.y));
                break;

            case WM_RBUTTONDOWN:
                wndParent.PostMessage(WM_NCRBUTTONDOWN, nHitTest, MAKELPARAM(ptMouse.x, ptMouse.y));
                break;

            case WM_RBUTTONUP:
                wndParent.PostMessage(WM_NCRBUTTONUP, nHitTest, MAKELPARAM(ptMouse.x, ptMouse.y));
                break;

            case WM_RBUTTONDBLCLK:
                wndParent.PostMessage(WM_NCRBUTTONDBLCLK, nHitTest, MAKELPARAM(ptMouse.x, ptMouse.y));
                break;

            case WM_MBUTTONDOWN:
                wndParent.PostMessage(WM_NCMBUTTONDOWN, nHitTest, MAKELPARAM(ptMouse.x, ptMouse.y));
                break;

            case WM_MBUTTONUP:
                wndParent.PostMessage(WM_NCMBUTTONUP, nHitTest, MAKELPARAM(ptMouse.x, ptMouse.y));
                break;

            case WM_MBUTTONDBLCLK:
                wndParent.PostMessage(WM_NCMBUTTONDBLCLK, nHitTest, MAKELPARAM(ptMouse.x, ptMouse.y));
                break;
            }
        }

        return 0;
    }

    BOOL OnEraseBkgnd(HDC dc)
    {
        return TRUE;
    }

    void OnPaint(HDC)
    {
        CPaintDC dcPaint(m_hWnd);
        int nContextState = dcPaint.SaveDC();
        CRect rcClient;
        GetClientRect(rcClient);
        CRect rcTitleTip(rcClient);

        if (m_bShowThemed && CTheme::IsThemingSupported()) {
            CPen penBorder;
            penBorder.CreatePen(PS_SOLID, 1, m_rgbBorderOuter);
            dcPaint.SelectPen(penBorder);
            dcPaint.SelectStockBrush(HOLLOW_BRUSH);
            dcPaint.RoundRect(rcTitleTip, CPoint(5, 5));
            rcTitleTip.DeflateRect(1, 1);
            CPen penInnerBorder;
            penInnerBorder.CreatePen(PS_SOLID, 1, m_rgbBorderInner);
            dcPaint.SelectPen(penInnerBorder);
            dcPaint.RoundRect(rcTitleTip, CPoint(2, 2));
            rcTitleTip.DeflateRect(1, 1);
            GRADIENT_RECT grdRect = { 0, 1 };
            TRIVERTEX triVertext[ 2 ] = {
                rcTitleTip.left,
                rcTitleTip.top,
                GetRValue(m_rgbBackgroundTop) << 8,
                GetGValue(m_rgbBackgroundTop) << 8,
                GetBValue(m_rgbBackgroundTop) << 8,
                0x0000,
                rcTitleTip.right,
                rcTitleTip.bottom,
                GetRValue(m_rgbBackgroundBottom) << 8,
                GetGValue(m_rgbBackgroundBottom) << 8,
                GetBValue(m_rgbBackgroundBottom) << 8,
                0x0000
            };
            dcPaint.GradientFill(triVertext, 2, &grdRect, 1, GRADIENT_FILL_RECT_V);
        } else {
            dcPaint.SetBkColor(m_rgbBackground);
            dcPaint.ExtTextOut(rcTitleTip.left, rcTitleTip.top, ETO_OPAQUE, rcTitleTip, _T(""), 0, NULL);
            CBrush bshTitleFrame;
            bshTitleFrame.CreateSolidBrush(m_rgbTextColour);
            dcPaint.FrameRect(rcTitleTip, bshTitleFrame);
        }

        int nTextLength = GetWindowTextLength() + 1;
        CString strItemText;
        GetWindowText(strItemText.GetBuffer(nTextLength), nTextLength);
        strItemText.ReleaseBuffer();
        dcPaint.SelectFont(m_fntTitleFont);
        dcPaint.SetTextColor(m_rgbTextColour);
        dcPaint.SetBkMode(TRANSPARENT);
        CRect rcItemText(rcClient);
        rcItemText.OffsetRect(4, 0);
        dcPaint.DrawText(strItemText, strItemText.GetLength(), rcItemText, DT_LEFT | DT_SINGLELINE | DT_NOPREFIX | DT_VCENTER);
        dcPaint.RestoreDC(nContextState);
    }
};
